package be.rivendale.ghetto;

import be.rivendale.geometry.AxisAlignedBoundingBox;
import be.rivendale.geometry.TriangleModel;
import be.rivendale.material.Color;
import be.rivendale.mathematics.Point;
import be.rivendale.mathematics.Ray;
import be.rivendale.mathematics.Triple;
import be.rivendale.mathematics.Vertex;

public class ExperimentalVoxelGrid {
	private VoxelModel voxelModel;
	private Point minBound;
	private Point maxBound;
	private int nx;
	private int ny;
	private int nz;

    public ExperimentalVoxelGrid(TriangleModel triangleModel) {
        long start = System.currentTimeMillis();
        this.voxelModel = new VoxelizerMarkThree().voxelize(triangleModel, new Triple(0.05, 0.05, 0.05), new Triple(-3, -2, 0));
        long stop = System.currentTimeMillis();
        System.out.println("Voxelization Mk3 time required: " + (stop - start) + "ms");

        minBound = voxelModel.getBottomLeftFrontPoint();
        maxBound = voxelModel.getTopRightBackPoint();
        nx = voxelModel.getXResolution();
        ny = voxelModel.getYResolution();
        nz = voxelModel.getZResolution();
    }

    public Color traverseVoxels(Ray ray) {
		Double mytmin = RayBoxIntersectionGhetto.rayBoxIntersection(ray, new AxisAlignedBoundingBox(minBound, maxBound));
		if(mytmin == null) {
			//System.out.println("no intersection");
			return null;
		}

		double tmin;
		if(mytmin < 0) {
			tmin = 0;
			throw new UnsupportedOperationException("So this is where this happens? Now I should understand and fix this!");
		} else {
			tmin = mytmin;
		}

		Point start   = ray.getOrigin().add(ray.direction().multiply(tmin).asPoint());
        Triple boxSize = (Triple) maxBound.subtract(minBound);

		int x = (int) (Math.floor( ((start.getX() - minBound.getX()) / boxSize.getX()) * nx ) + 1);
        int y = (int) (Math.floor( ((start.getY() - minBound.getY()) / boxSize.getY()) * ny ) + 1);
        int z = (int) (Math.floor( ((start.getZ() - minBound.getZ()) / boxSize.getZ()) * nz ) + 1);

        if(x == (nx + 1)) { x=x-1; }
        if(y == (ny + 1)) {  y=y-1; }
        if(z == (nz + 1)) {  z=z-1; }

		double tVoxelX;
		int stepX;
		if (ray.direction().getX() >= 0) {
			tVoxelX = ((double)(x)) / nx;
			stepX = 1;
		} else {
            tVoxelX = (((double)x)-1) / nx;
            stepX = -1;
		}

        double tVoxelY;
		int stepY;
		if (ray.direction().getY() >= 0) {
			tVoxelY = ((double)(y)) / ny;
			stepY = 1;
		} else {
            tVoxelY = (((double)y)-1) / ny;
            stepY = -1;
		}

        double tVoxelZ;
		int stepZ;
		if (ray.direction().getZ() >= 0) {
			tVoxelZ = ((double)(z)) / nz;
			stepZ = 1;
		} else {
            tVoxelZ = (((double)z)-1) / nz;
            stepZ = -1;
		}

        double voxelMaxX  = minBound.getX() + tVoxelX * boxSize.getX();
        double voxelMaxY  = minBound.getY() + tVoxelY * boxSize.getY();
        double voxelMaxZ  = minBound.getZ() + tVoxelZ * boxSize.getZ();

		double tMaxX = tmin + (voxelMaxX - start.getX()) / ray.direction().getX();
		double tMaxY = tmin + (voxelMaxY - start.getY()) / ray.direction().getY();
		double tMaxZ = tmin + (voxelMaxZ - start.getZ()) / ray.direction().getZ();

        double voxelSizeX = boxSize.getX() / nx;
        double voxelSizeY = boxSize.getY() / ny;
        double voxelSizeZ = boxSize.getZ() / nz;

		double tDeltaX = voxelSizeX / Math.abs(ray.direction().getX());
		double tDeltaY = voxelSizeY / Math.abs(ray.direction().getY());
		double tDeltaZ = voxelSizeZ / Math.abs(ray.direction().getZ());

		while(isVoxelCoordinateInsideGrid(x, y, z)) {
//            % ---------------------------------------------------------- %
//            % check if voxel [x,y,z] contains any intersection with the ray
//            %
//            %   if ( intersection )
//            %       break;
//            %   end;
//            % ---------------------------------------------------------- %


			// KEVIN'S STUFF
			// break on the first opaque voxel
			Voxel voxel = voxelModel.getAt(x, y, z);
			if(voxel != null) {
                Vertex light = new Vertex(-5, 0, -5, Color.WHITE);
                return Shader.shade(light, voxel.getNormal(), voxel.getColor(), getVoxelPosition(x, y, z));
			}


            if (tMaxX < tMaxY) {
                if (tMaxX < tMaxZ) {
                    x = x + stepX;
                    tMaxX = tMaxX + tDeltaX;
				} else {
                    z = z + stepZ;
                    tMaxZ = tMaxZ + tDeltaZ;
				}
			} else {
                if (tMaxY < tMaxZ) {
                    y = y + stepY;
                    tMaxY = tMaxY + tDeltaY;
				} else {
                    z = z + stepZ;
                    tMaxZ = tMaxZ + tDeltaZ;
				}
			}
		}

		return Color.WHITE.multiply(0.1);
	}

    private Point getVoxelPosition(int x, int y, int z) {
        return voxelModel.getBottomLeftFrontPoint().add(new Triple(
            x * voxelModel.getVoxelWidth(),
            y * voxelModel.getVoxelHeight(),
            z * voxelModel.getVoxelDepth()
        ));
    }

    private boolean isVoxelCoordinateInsideGrid(int x, int y, int z) {
		return x <= nx && x >= 1
				&& y <= ny && y >=1
				&& z <= nz && z >=1;
	}
}
